package com.molichuxing.services.infrastructure.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeAppPayModel;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradeAppPayRequest;
import com.alipay.api.response.AlipayTradeAppPayResponse;
import com.molichuxing.framework.exception.BizException;
import com.molichuxing.framework.utils.DateUtil;
import com.molichuxing.services.infrastructure.bean.EscrowLogBean;
import com.molichuxing.services.infrastructure.bean.EscrowNotifyLogBean;
import com.molichuxing.services.infrastructure.dao.EscrowLogMapper;
import com.molichuxing.services.infrastructure.dao.EscrowNotifyLogMapper;
import com.molichuxing.services.infrastructure.dto.request.create.AlipayCreateDto;
import com.molichuxing.services.infrastructure.dto.response.AlipayResultDto;
import com.molichuxing.services.infrastructure.dto.response.PrepaymentDto;
import com.molichuxing.services.infrastructure.service.AlipayService;
import com.molichuxing.services.property.EscrowLogTypeEnum;
import com.molichuxing.services.property.EscrowNotifyLogTypeEnum;
import com.molichuxing.services.property.PaymentChannelEnum;
import com.molichuxing.services.property.sdk.AlipayTradeStateEnum;
import com.molichuxing.services.sdk.alipay.AlipayConfig;
import com.molichuxing.services.utils.AlipayUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Map;

@Service("alipayService")
public class AlipayServiceImpl implements AlipayService {
    private static final Logger logger = LoggerFactory.getLogger(AlipayServiceImpl.class);

    @Resource
    private EscrowLogMapper escrowLogMapper;

    @Resource
    private EscrowNotifyLogMapper escrowNotifyLogMapper;

    /**
     * 统一下单(预支付单)
     *
     * @param createDto
     * @return
     * @throws Exception
     */
    @Override
    public PrepaymentDto orderPay(AlipayCreateDto createDto) throws Exception {
        // 获取配置
        AlipayConfig config = AlipayConfig.getInstance();
        // 生成请求参数
        AlipayTradeAppPayRequest reqData = AlipayUtil.orderPayParams(createDto, config);
        // 获得初始化的AlipayClient
        AlipayClient alipayClient = new DefaultAlipayClient(config.getGatewayUrl(), config.getAppId(),
                config.getPrivateKey(), config.getFormat(), config.getCharset(), config.getPublicKey(),
                config.getSignType());
        // 请求支付宝，并获取结果
        AlipayTradeAppPayResponse respData = alipayClient.sdkExecute(reqData);
        // 保存请求参数、响应参数
        saveEscrowLog(reqData, respData, EscrowLogTypeEnum.ORDER_PAY);

        // 判断响应结果
        if (!respData.isSuccess()) {
            // 失败
            logger.error("[orderPay]订单(订单号:{})调用支付宝生成预支付单失败：{}", createDto.getMerchantOrderNo(), respData.toString());
            throw new BizException("支付宝生成预支付单失败");
        }
        // 生成回显参数
        return AlipayUtil.orderPayResponseParams(config, reqData, respData);
    }

    // AlipayTradeQueryRequest 查询

    /**
     * 支付结果异步通知解析
     *
     * @param notifyData
     * @throws Exception
     */
    @Override
    public AlipayResultDto notifyPay(Map<String, String> notifyData) throws Exception {
        AlipayResultDto result = null;
        if (isOldFinished(notifyData)) {
            throw new BizException(110, "法体跟换之前异步通知数据");
        }
        // 获取配置
        AlipayConfig config = AlipayConfig.getInstance();
        // 调用SDK验证签名
        boolean signVerified = false;
        try {
            signVerified = AlipaySignature.rsaCheckV1(notifyData, config.getPublicKey(), config.getCharset(),
                    config.getSignType());
        } catch (Exception e) {
            logger.error("[notifyPay]验签失败");
            throw new BizException("支付宝支付回调验签失败");
        }

        if (!signVerified) {
            // 验签失败
            logger.error("[notifyPay]支付宝支付回调失败：{}", notifyData);
            throw new BizException("支付宝支付回调验签失败:");
        }
        // 校验参数信息是否一致(seller_id、app_id)
        if (!config.getAppId().equals(notifyData.get("app_id"))
                || !config.getSellerId().equals(notifyData.get("seller_id"))) {
            logger.error("[notifyPay]支付宝支付回调失败(app_id或seller_id不一致)：{}", notifyData);
            return result;
        }
        // 保存回调日志
        saveNotifyLog(notifyData, EscrowNotifyLogTypeEnum.PAY);

        result = new AlipayResultDto();
        result.setOutTradeNo(notifyData.get("out_trade_no"));
        result.setTradeNo(notifyData.get("trade_no"));
        result.setTotalAmount(new BigDecimal(notifyData.get("total_amount")));
        result.setTradeState(AlipayTradeStateEnum.getEnum(notifyData.get("trade_status")));
        result.setPassbackParams(notifyData.get("passback_params"));
        if (AlipayTradeStateEnum.TRADE_SUCCESS.equals(result.getTradeState())) {
            result.setGmtPayment(DateUtil.parseDateTime(notifyData.get("gmt_payment"), "yyyy-MM-dd HH:mm:ss"));
        }

        return result;
    }

    /**
     * 保存请求日志
     *
     * @param reqData
     * @param respData
     */
    @Transactional(propagation = Propagation.NOT_SUPPORTED, isolation = Isolation.DEFAULT, timeout = 36000, rollbackFor = Exception.class)
    public void saveEscrowLog(AlipayTradeAppPayRequest reqData, AlipayTradeAppPayResponse respData,
                              EscrowLogTypeEnum type) {
        EscrowLogBean escrowLogBean = new EscrowLogBean();
        AlipayTradeAppPayModel payModel = (AlipayTradeAppPayModel) reqData.getBizModel();
        escrowLogBean.setPayNumber(payModel.getOutTradeNo());
        escrowLogBean.setRequestData(JSONObject.toJSONString(reqData));
        escrowLogBean.setResponseData(JSONObject.toJSONString(respData));
        escrowLogBean.setChannel(PaymentChannelEnum.ALIPAY.getValue());
        escrowLogBean.setType(type.getValue());
        escrowLogBean.setRemark(type.getName());
        escrowLogMapper.create(escrowLogBean);
    }

    /**
     * 保存回调日志
     *
     * @param notifyData
     * @param type
     */
    @Transactional(propagation = Propagation.NOT_SUPPORTED, isolation = Isolation.DEFAULT, timeout = 36000, rollbackFor = Exception.class)
    public void saveNotifyLog(Map<String, String> notifyData, EscrowNotifyLogTypeEnum type) {
        try {
            EscrowNotifyLogBean escrowNotifyLogBean = new EscrowNotifyLogBean();
            escrowNotifyLogBean.setPayNumber(notifyData.get("out_trade_no"));
            escrowNotifyLogBean.setChannel(PaymentChannelEnum.ALIPAY.getValue());
            escrowNotifyLogBean.setType(type.getValue());
            escrowNotifyLogBean.setNotifyData(JSONObject.toJSONString(notifyData));
            escrowNotifyLogMapper.create(escrowNotifyLogBean);
        } catch (Exception e) {
            logger.error("[saveNotifyLog]支付宝({}:{})回调保存日志异常", type.getValue(), type.getName());
        }
    }

    // 判断是否是跟换法体前的通知
    private boolean isOldFinished(Map<String, String> notifyData) {
        String gmtPayment = notifyData.get("gmt_payment");
        if (StringUtils.isBlank(gmtPayment)) {
            return true;
        }
        LocalDateTime payTime = DateUtil.parseDateTime(gmtPayment);
        LocalDateTime time = DateUtil.parseDateTime("2020-06-05 16:20:00");
        if (payTime.isBefore(time)) {
            return true;
        }
        return false;
    }

}
